/**
 * www.easyplatform.cn ©2016
 */
package cn.easyplatform.studio.cfg;

import cn.easyplatform.entities.beans.ResourceBean;
import cn.easyplatform.lang.Lang;
import cn.easyplatform.lang.Mirror;
import cn.easyplatform.lang.Nums;
import cn.easyplatform.studio.StudioApp;
import cn.easyplatform.studio.StudioException;
import cn.easyplatform.studio.cfg.jdbc.JdbcTransactionContextFactory;
import cn.easyplatform.studio.cfg.serial.StdIdGeneratorFactory;
import cn.easyplatform.studio.cfg.session.StdSessionManagerFactory;
import cn.easyplatform.studio.cfg.sync.StdEntityLockProviderFactory;
import cn.easyplatform.studio.dos.LockDo;
import cn.easyplatform.studio.interceptor.*;
import cn.easyplatform.studio.utils.AttributeCnUtil;
import cn.easyplatform.studio.vos.LoginUserVo;
import cn.easyplatform.studio.web.controller.admin.LoginLogListener;
import cn.easyplatform.studio.web.servlet.WebSessionListener;
import com.alibaba.druid.pool.DruidDataSource;
import org.zkoss.util.resource.Labels;
import org.zkoss.zk.ui.WebApp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author <a href="mailto:shiny_vc@163.com">陈云亮</a> <br/>
 * @since 2.0.0 <br/>
 */
public class EngineConfiguration {

    private static Logger log = LoggerFactory.getLogger(EngineConfiguration.class);

    // ///////////具体执行者///////////////////
    private CommandExecutor commandExecutor;

    private Properties properties = new Properties();

    private DruidDataSource datasource;

    private IdGeneratorFactory idGeneratorFactory;

    private TransactionContextFactory transactionContextFactory;

    private EntityLockProviderFactory entityLockProviderFactory;

    private SessionManagerFactory sessionManagerFactory;

    private WebApp webapp;

    private Map<String, DruidDataSource> bizDatasources;

    private transient boolean startFlag;

    public void init(WebApp webapp) throws Exception {
        this.webapp = webapp;
        bizDatasources = new HashMap<String, DruidDataSource>();
        loadConfig();
        initCommandExecutors();
        idGeneratorFactory = new StdIdGeneratorFactory();
        transactionContextFactory = new JdbcTransactionContextFactory();
        entityLockProviderFactory = new StdEntityLockProviderFactory();
        sessionManagerFactory = new StdSessionManagerFactory();
        webapp.getConfiguration().addListener(WebSessionListener.class);

        webapp.getConfiguration().setSessionMaxInactiveInterval(
                Nums.toInt(properties.get("app.sessionTimeout"), 1800));
        webapp.getConfiguration().setTimerKeepAlive(
                properties.getProperty("app.sessionKeepAlive")
                        .equalsIgnoreCase("true"));
        startFlag = true;
        // 记录锁
        webapp.setAttribute("ep_entity_lock",
                new ConcurrentHashMap<String, LockDo>());
        // 简单的会话客理
        final int sessionValidationInterval = Nums.toInt(
                properties.get("app.sessionValidationInterval"), 60) * 1000;
        final Map<String, LoginUserVo> sess = new ConcurrentHashMap<String, LoginUserVo>();
        webapp.setAttribute("ep_login_user", sess);
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                Lang.sleep(sessionValidationInterval);
                while (startFlag) {
                    synchronized (sess) {
                        for (LoginUserVo user : sess.values()) {
                            if (!user.validate()) {
                                LoginLogListener.timeOutLog(user);//记录用户退出
                                sess.remove(user.getUserId());
                                Map<String, LockDo> repo = (Map<String, LockDo>)getAttribute("ep_entity_lock");
                                synchronized (repo) {
                                    Iterator<LockDo> itr = repo.values().iterator();
                                    StringBuilder sb = new StringBuilder();
                                    while (itr.hasNext()) {
                                        LockDo li = itr.next();
                                        if (li.getUserId().equals(user.getUserId())) {
                                            sb.setLength(0);
                                            sb.append(li.getProjectId()).append(":")
                                                    .append(li.getEntityId());
                                            repo.remove(sb.toString());
                                        }
                                    }
                                    sb = null;
                                }
                                log.debug("remove user from session manager:%s", user.getUserId());
                            }
                        }
                    }
                    Lang.sleep(sessionValidationInterval);
                }
            }
        });
        t.setDaemon(true);
        t.start();
    }

    private void loadConfig() {
        FileInputStream input;
        try {
            input = new FileInputStream(new File(StudioApp.getServletContext()
                    .getRealPath("/WEB-INF/config/easyplatform.conf")));
            properties.load(input);
            AttributeCnUtil.init();

        } catch (FileNotFoundException e) {
            throw new StudioException(Labels.getLabel("app.config.error",
                    new Object[]{"easyplatform.conf not found."}));
        } catch (IOException ex) {
            throw new StudioException(Labels.getLabel("app.config.error",
                    new Object[]{ex}));
        }
        datasource = new MyDataSource();
        Set<String> em = properties.stringPropertyNames();
        try {
            Mirror<DruidDataSource> me = Mirror.me(DruidDataSource.class);
            for (String prop : em) {
                if (prop.startsWith("dbcp")) {
                    String name = prop.substring(5);
                    if (name.equals("jdbcUrl"))
                        datasource.setUrl(properties.getProperty(prop));
                    else
                        me.getInjecting(name).inject(datasource,
                                properties.getProperty(prop));
                }
            }
            bizDatasources.put("sys.datasource.entity", datasource);
        } catch (Exception ex) {
            try {
                datasource.close();
            } catch (Exception e) {
            }
            throw new StudioException(Labels.getLabel("app.datasource.error"),
                    ex);
        }
    }

    /**
     * 初始化命令执行链
     */
    private void initCommandExecutors() {
        List<CommandInterceptor> interceptors = new ArrayList<CommandInterceptor>();
        interceptors.add(new LogInterceptor());// 先写log
        interceptors.add(new CommandContextInterceptor(
                new CommandContextFactory(), this));// 设置当前操作的Context
        interceptors.add(new CommandInvoker());// 最终的调用
        for (int i = 0; i < interceptors.size() - 1; i++) {
            interceptors.get(i).setNext(interceptors.get(i + 1));
        }
        CommandInterceptor first = interceptors.get(0);// 先从LogInterceptor开始
        commandExecutor = new CommandExecutorImpl(first);// 给所有实现ServiceImpl对象使用
        //commandExecutor.execute(new InitVersionCmd());
        interceptors.clear();
        interceptors = null;
    }

    /**
     * @return
     */
    public IdGeneratorFactory getIdGeneratorFactory() {
        return idGeneratorFactory;
    }

    /**
     * @return
     */
    public TransactionContextFactory getTransactionContextFactory() {
        return transactionContextFactory;
    }

    /**
     * @return
     */
    public EntityLockProviderFactory getEntityLockProviderFactory() {
        return entityLockProviderFactory;
    }

    /**
     * @return
     */
    public SessionManagerFactory getSessionManagerFactory() {
        return sessionManagerFactory;
    }

    /**
     * @param name
     * @param value
     */
    public void setAttribute(String name, Object value) {
        webapp.setAttribute(name, value);
    }

    /**
     * @param name
     * @return
     */
    public Object getAttribute(String name) {
        return webapp.getAttribute(name);
    }

    /**
     * @param name
     * @return
     */
    public Object removeAttribute(String name) {
        return webapp.removeAttribute(name);
    }

    /**
     *
     */
    public void close() {
        startFlag = false;
        webapp.getAttributes().clear();
        for (DruidDataSource ds : bizDatasources.values())
            ds.close();
    }

    /**
     * @param command
     * @return
     */
    public <T> T execute(Command<T> command) {
        return commandExecutor.execute(command);
    }

    /**
     * @return
     */
    public DataSource getDatasource() {
        return datasource;
    }

    /**
     * @param bean
     * @return
     */
    public DataSource getDatasource(ResourceBean bean) {
        DataSource ds = bizDatasources.get(bean.getId());
        if (ds != null)
            return ds;
        else {
            synchronized (this) {
                DruidDataSource tmp = new MyDataSource();
                Mirror<DruidDataSource> me = Mirror.me(DruidDataSource.class);
                for (String methodName : bean.getProps().keySet()) {
                    if (!methodName.equals("master")) {
                        try {
                            me.getInjecting(methodName).inject(tmp,
                                    bean.getProps().get(methodName).trim());
                        } catch (Exception ex) {
                            log.error("setDatasource", ex);
                        }
                    }
                }
                tmp.setInitialSize(Nums.toInt(
                        properties.getProperty("dbcp.initialSize"), 1));
                tmp.setMaxActive(Nums.toInt(
                        properties.getProperty("dbcp.maxActive"), 20));
                tmp.setMinIdle(Nums.toInt(
                        properties.getProperty("dbcp.minIdle"), 1));
                tmp.setMaxIdle(Nums.toInt(
                        properties.getProperty("dbcp.maxIdle"), 5));
                bizDatasources.put(bean.getId(), tmp);
                return tmp;
            }
        }
    }

    /**
     * @param name
     * @return
     */
    public String getConfig(String name) {
        return properties.getProperty(name);
    }

    private class MyDataSource extends DruidDataSource {
        public String toString() {
            return this.getUrl();
        }
    }
}
